package com.sprite.framework.entity.script;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import com.sprite.framework.entity.DataScriptStatement;
import com.sprite.framework.entity.EntityCondition;
import com.sprite.framework.entity.EntityException;
import com.sprite.framework.entity.EntityFindOptions;
import com.sprite.framework.entity.EntityScript;
import com.sprite.framework.entity.condition.EmptyCondition;
import com.sprite.framework.entity.condition.MultiOperateValue;
import com.sprite.framework.entity.model.ModelEntityUtil;
import com.sprite.framework.entity.model.ModelEntityView;
import com.sprite.framework.entity.model.ModelField;
import com.sprite.utils.UtilArray;
import com.sprite.utils.UtilString;

/**
 * 实体视图
 * @author Jack
 */
public class EntityView implements MultiOperateValue, EntityFieldAliasBuilder, EntityScript, ModelEntityView{

	/**
	 * view fields
	 */
	private List<EntityFieldAlias> viewFields = new LinkedList<EntityFieldAlias>();

	private List<EntityFieldAlias> groupByFields = new ArrayList<EntityFieldAlias>();

	private List<EntityAlias> memberEntities = new LinkedList<EntityAlias>();

	private List<EntityViewLink> links = new LinkedList<EntityViewLink>();

	private boolean distinct = false;				// 是否去重
	private EntityCondition whereCondition;			// 查询条件
	private EntityCondition havingCondition;		// having	

	private List<EntityFieldAlias> orderByFields = new ArrayList<EntityFieldAlias>();

	private EntityFindOptions options;

	/**
	 *	 实体集合, key is alias
	 */
	private Map<String, EntityAlias> entityMap = new HashMap<String, EntityAlias>();

	/**
	 * key is alias
	 */
	private Map<String, EntityViewLink> linkMap = new HashMap<String,EntityViewLink>();

	/**
	 * key is alias
	 */
	private Map<String, EntityFieldAlias> fieldMap = new HashMap<>();


	public EntityView() {
	}

	public EntityView(String entityName, String entityAlias) {
		addMemberEntity(entityName, entityAlias);
	}

	/**
	 * 	添加 member entity
	 * <p>select * from m1,m2</p>
	 * @param entityName 实体名称
	 * @param entityAlias 实体别名
	 */
	public void addMemberEntity(String entityName, String entityAlias) {
		if(entityName == null){
			throw new IllegalArgumentException("entityAlias  must");
		}

		if(entityAlias == null){
			throw new IllegalArgumentException("entity must");
		}

		if(entityMap.containsKey(entityAlias)){
			throw new IllegalArgumentException("EntityAlias [" +entityAlias+"] is exist");
		}

		EntityAlias alias = new EntityAlias(entityName, entityAlias);
		entityMap.put(entityAlias, alias);
		memberEntities.add(alias);
	}

	/**
	 * Left Join Entity
	 * @param entityView  实体视图
	 * @param alias 实体别名
	 * @param condition  查询条件
	 */
	public void leftJoin(EntityView entityView,String alias, EntityCondition condition){
		join("LEFT JOIN", entityView, alias, condition);
	}

	/**
	 * Left Join Entity
	 * @param entityName  实体名称
	 * @param alias 实体别名
	 * @param condition  查询条件
	 */
	public void leftJoin(String entityName,String alias, EntityCondition condition){
		join("LEFT JOIN", entityName, alias, condition);
	}

	/**
	 * INNER Join Entity
	 * @param entityView 实体视图
	 * @param alias 别名
	 * @param condition 条件
	 */
	public void innerJoin(EntityView entityView,String alias, EntityCondition condition){
		join("INNER JOIN", entityView, alias, condition);
	}

	/**
	 * INNER Join Entity
	 * @param entityName 实体名称
	 * @param alias 别名
	 * @param condition 条件
	 */
	public void innerJoin(String entityName,String alias, EntityCondition condition){
		join("LEFT JOIN", entityName, alias, condition);
	}

	private void join(String code, EntityView entityView,String alias, EntityCondition condition){
		if(UtilString.isBlank(alias)){
			throw new IllegalArgumentException("alias must ");
		}

		if(linkMap.containsKey(alias)){
			throw new IllegalArgumentException("EntityView [" +alias+"] is joined ");
		}

		EntityViewLink link = new EntityViewLink(code, entityView,alias, condition);
		links.add(link);
		linkMap.put(alias, link);
	}


	private void join(String code, String entityName, String alias, EntityCondition condition){
		if(UtilString.isBlank(alias)){
			throw new IllegalArgumentException("alias must ");
		}

		if(linkMap.containsKey(alias)){
			throw new IllegalArgumentException("EntityView [" +alias+"] is joined ");
		}

		EntityViewLink link = new EntityViewLink(code, entityName,alias, condition);
		links.add(link);
		linkMap.put(alias, link);
	}

	private void addField(String entityAlias, String field, String fieldAlias, EntityScriptFunction function){
		EntityFieldAlias alias = getEntityFieldAliasBuilder(entityAlias).buildFieldAlias(field, fieldAlias, function);
		alias.setEntityAlias(entityAlias);
		if(alias.getFieldAlias() == null) {
			alias.setFieldAlias(field);
		}
		viewFields.add(alias);
		fieldMap.put(alias.getFieldAlias(), alias);
	}

	/**
	 * 	添加View Field
	 * @param entityAlias 实体别名
	 * @param field	字段
	 * @param fieldAlias	字段别名
	 * @param function	函数
	 */
	public void addViewFieldAs(String entityAlias, String field, String fieldAlias, EntityScriptFunction function){
		if(UtilString.isBlank(fieldAlias)){
			throw new IllegalArgumentException("fieldAlias must, if you not provide alias ,to see addViewField ");
		}

		addField(entityAlias, field, fieldAlias, function);
	}


	/**
	 *
	 * @param entityAlias 实体别名
	 * @param fields 字段
	 */
	public void addViewFieldAs(String entityAlias, String... fields){
		if(UtilArray.isEmpty(fields) || fields.length%2 != 0) {
			throw new IllegalArgumentException("fields expect a value and length is multiple of 2 ");
		}

		for(int i=0; i<= fields.length-2; i+=2){
			addField(entityAlias, fields[i], fields[i+1], null);
		}

	}

	
	/**
	 * 	添加View Field
	 * @param entityAlias 实体别名
	 * @param field 字段
	 * @param function 函数
	 */
	public void addViewField(String entityAlias, String field, EntityScriptFunction function){
		addField(entityAlias, field, field, function);
	}

	/**
	 *	添加View Field 
	 * @param entityAlias 实体别名
	 * @param fields 字段
	 */
	public void addViewField(String entityAlias, String... fields){
		if(!UtilArray.isEmpty(fields)) {
			for(String field : fields){
				addField(entityAlias, field, field, null);
			}
		}else {
			addAllField(entityAlias);
		}
	}
	
	/**
	 *	添加 Entity 所有的Field
	 * @param entityAlias
	 */
	private void addAllField(String entityAlias){
		List<EntityFieldAlias> aliasList = getEntityFieldAliasBuilder(entityAlias).buildAllFieldAlias();

		for(EntityFieldAlias alias : aliasList) {
			alias.setEntityAlias(entityAlias);

			if(alias.getFieldAlias() == null) {
				alias.setFieldAlias(alias.getFieldName());
			}
			viewFields.add(alias);
			fieldMap.put(alias.getFieldAlias(), alias);
		}
	}


	/**
	 * 
	 * @param entityAlias 实体别名
	 * @param fields 字段
	 */
	public void addGroupByField(String entityAlias, String... fields){
		if(UtilArray.isEmpty(fields)) {
			return;
		}
		
		for(String field : fields) {
			EntityFieldAlias alias = getEntityFieldAliasBuilder(entityAlias).buildFieldAlias(field, null, null);
			alias.setEntityAlias(entityAlias);
			groupByFields.add(alias);
		}
	}

	/**
	 * 
	 * @param entityAlias 实体别名
	 * @param fields 字段
	 */
	public void addOrderByDesc(String entityAlias, String... fields){
		if(UtilArray.isEmpty(fields)) {
			return;
		}
		
		for(String field : fields) {
			EntityFieldAlias alias = getEntityFieldAliasBuilder(entityAlias).buildFieldAlias(field, null, null);
			alias.setEntityAlias(entityAlias);
			orderByFields.add(alias);
		}
	}

	/**
	 * 
	 * @param entityAlias 实体别名
	 * @param fields 字段
	 */
	public void addOrderByAsc(String entityAlias, String... fields){
		if(UtilArray.isEmpty(fields)) {
			return;
		}
		
		for(String field : fields) {
			EntityFieldAlias alias = getEntityFieldAliasBuilder(entityAlias).buildFieldAlias(field, null, null);
			alias.setEntityAlias(entityAlias);
			alias.setIsASC();
			orderByFields.add(alias);
		}
	}
	
	public EntityScript countScript() {
		DataScriptStatement statement = new DataScriptStatement();
		statement.append("SELECT");

		statement.append(" COUNT(*) ");

		makeFromFragment(statement, this);

		if(whereCondition != null && !EmptyCondition.class.isInstance(whereCondition)){
			statement.append(" WHERE ");
			whereCondition.makeScript(statement, this);
		}

		makeGroupByFragment(statement, this);

		if(havingCondition != null){
			statement.append(" HAVING ");
			havingCondition.makeScript(statement, this);
		}

		makeOrderByFragment(statement, this);

		makeOffset(statement, this);

		return statement;
	}

	/**
	 *
	 * @param entityAlias 实体字别名
	 * @param field	count（field）
	 * @return count 脚本
	 */
	public EntityScript count(String entityAlias, String field) {
		EntityView countView = new EntityView();
		countView.distinct = this.distinct;
		countView.memberEntities = this.memberEntities;
		countView.groupByFields = this.groupByFields;
		countView.memberEntities = this.memberEntities;
		countView.links = this.links;
		countView.addViewFieldAs(entityAlias, field, null, EntityScriptFunction.COUNT);
		countView.whereCondition = this.whereCondition;
		return countView;
	}

	public void makeStatement(DataScriptStatement statement) throws EntityException {
		makeStatement(statement, this);
	}

	@Override
	public void makeStatement(DataScriptStatement statement, ModelEntityView modelViewEntity) throws EntityException {
		makeSelectFragment(statement, modelViewEntity);

		makeFromFragment(statement, modelViewEntity);

		if(whereCondition != null && !EmptyCondition.class.isInstance(this.whereCondition)){
			if(viewFields != null && !viewFields.isEmpty()){
				statement.append(" WHERE");
			}

			whereCondition.makeScript(statement, modelViewEntity);
		}

		makeGroupByFragment(statement, modelViewEntity);

		if(havingCondition != null){
			statement.append(" HAVING ");
			havingCondition.makeScript(statement, modelViewEntity);
		}

		makeOrderByFragment(statement, modelViewEntity);

		makeOffset(statement, modelViewEntity);
	}


	private void makeSelectFragment(DataScriptStatement statement, ModelEntityView modelViewEntity) throws EntityException{
		if(viewFields == null || viewFields.isEmpty()){
			return;
		}
		statement.append("SELECT");
		if(distinct){
			statement.append(" DISTINCT ");
		}

		int length = viewFields.size();


		for(EntityFieldAlias fieldAlias : viewFields){
			length--;

			statement.append(" ");
			if(modelViewEntity != null){
				statement.append(fieldAlias.function(modelViewEntity.resolveFieldPath(fieldAlias.getFieldPath())));
				if(fieldAlias.getFieldAlias() != null) {
					statement.append(" AS ").append(fieldAlias.getFieldAlias());
				}
			}else{
				if(fieldAlias.getFunction() != null){
					statement.append(fieldAlias.function(fieldAlias.getFieldName()));

					if(fieldAlias.getFieldAlias() != null){
						statement.append(" ").append(fieldAlias.getFieldAlias());
					}
				}else{
					statement.append(" ").append(fieldAlias.getEntityAlias()).append(".").append(fieldAlias.getFieldName());
					if(fieldAlias.getFieldAlias() != null){
						statement.append(" AS ").append(fieldAlias.getFieldAlias());
					}else{
						//statement.append(" ").append(fieldAlias.getFieldName());
					}
				}
			}

			if(length > 0){
				statement.append(",");
			}
		}
	}

	private void makeFromFragment(DataScriptStatement statement, ModelEntityView modelViewEntity) throws EntityException{
		if(memberEntities.isEmpty() && links.isEmpty()){
			return;
		}
		statement.append(" FROM");

		int length = memberEntities.size();


		for(EntityAlias entry : memberEntities){
			length--;

			statement.append(" ");
			statement.append(ModelEntityUtil.getModelEntity(entry.getEntityName()).getTableName());
			statement.append(" AS ").append(entry.getEntityAlias());

			if(length > 0){
				statement.append(",");
			}
		}

		for(EntityViewLink link : links){
			statement.append(" ");
			statement.append(link.code);
			link.makeStatement(statement, modelViewEntity);
			statement.append(" ON ");
			link.onCondition.makeScript(statement, modelViewEntity);
		}
	}

	private void makeGroupByFragment(DataScriptStatement statement, ModelEntityView modelViewEntity) throws EntityException{

		int length = groupByFields.size();
		boolean hasGroupBy = false;
		for(EntityFieldAlias fieldAlias : groupByFields){
			length--;

			if(!hasGroupBy){
				statement.append(" GROUP BY");
				hasGroupBy = true;
			}

			statement.append(" ");

			if(modelViewEntity != null){
				statement.append(modelViewEntity.resolveFieldPath(fieldAlias.getFieldPath()));
			}else{
				String entityAlias = fieldAlias.getEntityAlias();
				String field = fieldAlias.getFieldName();
				String prefix = entityAlias+".";

				statement.append(prefix).append(field);
			}

			if(length > 0){
				statement.append(",");
			}

		}
	}

	/**
	 * 	分页设置
	 * @param statement
	 * @param modelViewEntity
	 */
	private void makeOffset(DataScriptStatement statement, ModelEntityView modelViewEntity) {
		if(options == null) {
			return;
		}

		if(ModelEntityUtil.DATABASE_TYPE_MYSQL.equals(ModelEntityUtil.getDatabaseType())) {
			statement.append(" LIMIT ").append(options.getOffset()+",").append(options.getLimit()+"");
			return;
		}else if(ModelEntityUtil.DATABASE_TYPE_SQLSERVER2012.equals(ModelEntityUtil.getDatabaseType())) {
			statement.append(" OFFSET ").append(options.getOffset()+" ROW FETCH NEXT ").append(options.getLimit()+" ROWS only");
			return;
		}else if(ModelEntityUtil.DATABASE_TYPE_PGSQL.equals(ModelEntityUtil.getDatabaseType())) {
			statement.append(" LIMIT ").append(options.getLimit()+" OFFSET ").append(options.getOffset()+" ");
			return;
		}

		DataScriptStatement scriptStatement = new DataScriptStatement();
		scriptStatement.addParams(statement.getParams());

		if(ModelEntityUtil.DATABASE_TYPE_SQLSERVER.equals(ModelEntityUtil.getDatabaseType())) {
			String temp = "SELECT TOP {} _rn.* from (SELECT row_number() over({}) as _rnum,* from({}) as _rf) as _rn WHERE _rnum>{}";

			temp = UtilString.place(temp, options.getOffset(), makeOrderByFragment(modelViewEntity), scriptStatement.toScriptString(), options.getOffset());

			scriptStatement.append(temp);
		}else if(ModelEntityUtil.DATABASE_TYPE_ORACLE.equals(ModelEntityUtil.getDatabaseType())) {
			String temp = "SELECT * FROM(SELECT a.*,ROWNUM _rnum FROM({}) _rn WHERE ROWNUM<=({})) WHERE _rnum>{}";

			temp = UtilString.place(temp,scriptStatement.toScriptString(), options.getOffset()+options.getLimit(), options.getOffset());
		}

		statement.clear();
		statement.append(scriptStatement.toScriptString());
		statement.addParams(scriptStatement.getParams());
	}

	/**
	 * 	排序
	 * @param statement
	 * @param modelViewEntity
	 */
	private void makeOrderByFragment(DataScriptStatement statement, ModelEntityView modelViewEntity) {
		statement.append(makeOrderByFragment(modelViewEntity));
	}

	private String makeOrderByFragment(ModelEntityView modelViewEntity) {
		StringBuilder orderStr = new StringBuilder();

		int length = orderByFields.size();
		boolean has = false;
		for(EntityFieldAlias fieldAlias : orderByFields){
			length--;

			boolean asc = Boolean.TRUE.equals(fieldAlias.getIsASC());
			String order = asc?"ASC":"DESC";

			if(!has){
				orderStr.append(" ORDER BY ");
				has = true;
			}

			orderStr.append(" ");

			if(modelViewEntity != null){
				orderStr.append(modelViewEntity.resolveFieldPath(fieldAlias.getFieldPath()));
			}else{
				String entityAlias = fieldAlias.getEntityAlias();
				String field = fieldAlias.getFieldName();
				String prefix = entityAlias+".";

				orderStr.append(prefix).append(field);
			}

			orderStr.append(" ").append(order);

			if(length > 0){
				orderStr.append(",");
			}
		}

		return orderStr.toString();
	}

	private void checkEntityAlias(String entityAlias) {
		if(!entityMap.containsKey(entityAlias) && !linkMap.containsKey(entityAlias)){
			throw new IllegalArgumentException("EntityAlias [" +entityAlias+"] is not exist");
		}
	}

	private EntityFieldAliasBuilder getEntityFieldAliasBuilder(String entityAlias) {
		checkEntityAlias(entityAlias);

		if(entityMap.get(entityAlias) != null) {
			return entityMap.get(entityAlias);
		}else {
			return linkMap.get(entityAlias);
		}
	}

	public void distinct() {
		this.distinct = true;
	}

	public void setWhereCondition(EntityCondition whereCondition) {
		this.whereCondition = whereCondition;
	}
	public void setHavingCondition(EntityCondition havingCondition) {
		this.havingCondition = havingCondition;
	}

	public void setOptions(EntityFindOptions options) {
		this.options = options;
	}

	@Override
	public EntityFieldAlias buildFieldAlias(String fieldName, String fieldAlias, EntityScriptFunction function) {
		EntityFieldAlias alias = fieldMap.get(fieldName);

		EntityFieldAlias fiAlias = new EntityFieldAlias(null, fieldName, fieldAlias, function, null);
		ModelField modelField = new ModelField(fieldName, alias.getFieldAlias(), null);
		fiAlias.setModelField(modelField);
		return fiAlias;
	}

	@Override
	public List<EntityFieldAlias> buildAllFieldAlias() {
		List<EntityFieldAlias> list = new LinkedList<>();
		for(EntityFieldAlias fieldAlias : viewFields) {
			list.add(buildFieldAlias(fieldAlias.getFieldAlias(), fieldAlias.getFieldAlias(), null));
		}
		return list;
	}

	@Override
	public DataScriptStatement getStatement() {
		DataScriptStatement statement = new DataScriptStatement();
		makeStatement(statement, this);
		return statement;
	}

	@Override
	public boolean hasField(String field) {
		for(EntityFieldAlias fieldAlias : viewFields) {
			if(fieldAlias.getFieldName().equalsIgnoreCase(field)) {
				return true;
			}
		}
		return false;
	}

	@Override
	public String resolveFieldPath(String path) throws EntityException{
		String entityAlias = null;
		String field = path;

		{
			int i = path.lastIndexOf(".");
			if(i > 0){
				entityAlias = path.substring(0, i);
				field = path.substring(i+1);
			}
		}

		if(entityAlias == null) {
			EntityAlias ev = null;
			for(EntityAlias entity : memberEntities) {
				if(!entity.hasField(field)) {
					continue;
				}
				if(ev == null) {
					ev = entity;
				}else {
					throw new EntityException("must point entityAlis for field");
				}
			}

			if(ev == null) {
				throw new EntityException(String.format("[%s] field not exist", path));
			}

			return ev.resolveFieldPath(field);
		}

		return resolveField(entityAlias, field);
	}

	/**
	 * 
	 * @param entityAlias
	 * @param field
	 * @return
	 */
	private String resolveField(String entityAlias, String field) {
		EntityFieldAliasBuilder modelEntity = entityMap.get(entityAlias);

		if(modelEntity == null){
			modelEntity = linkMap.get(entityAlias);
		}

		if(modelEntity == null) {
			throw new EntityException("ModelField  ["+entityAlias+"] is not exist");
		}
		String column = modelEntity.resolveFieldPath(field);
		return String.format("%s.%s",entityAlias, column);
	}
}
